Skip to content

feat(deeplink): add the notification, settings and billing routes - #146

Merged
kipavy merged 1 commit into
devfrom
feat/deeplink-part3-navigate
Aug 18, 2026
Merged

feat(deeplink): add the notification, settings and billing routes#146
kipavy merged 1 commit into
devfrom
feat/deeplink-part3-navigate

Conversation

@kipavy

@kipavy kipavy commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Part 3 of #144, the navigate half. Companion page change: VoltiusApp/web#13 (merge this first — the two whitelists must agree or a link dies in silence).

A third trust class

TRUST gains navigate beside confirm and silent. A navigate route only moves the user to a screen they could already reach, so it needs no prompt — but it must never act: billing opens the account section and deliberately does not start a checkout. RouteOfClass already derives the type split, so NavigateIntent falls out of the existing machinery; UnpromptedIntent is the union of silent and navigate, and the store dispatches by trust class rather than by a second list of route names.

route params effect
notification optional opaque n, capped at 200 chars opens the notification centre, scrolled to the entry
settings section, checked against the real section list openSettings(section)
billing none openSettings("account")

Two things the design turned up

mcp is not a settings section. #144 listed settings?section=mcp as the notable case, but MCP settings live inside integrations. Rather than trust the string, SettingsSection is inverted: SETTINGS_SECTIONS is now the runtime source of truth and the type derives from it, so the codec checks a section instead of casting one. #settings?section=mcp is rejected, with a test saying why.

Notification ids are not durable. Inbox entries are re-derived from server state on every reconcile (teamInbox.ts), so a link can only name an id, never guarantee it. An unknown id opens the centre on the full list rather than reporting an error.

The bell

NotificationBell held its popover in local useState, so nothing outside could open it; that moves to uiStore. The mobile shell keeps a second bell mounted behind invisible (MobileSftpScreen asTab) and the popover is portalled to the body, where an ancestor's visibility no longer hides it — so each bell measures its own computed visibility on open and the off-screen one leaves the popover to the visible one. Placement is measured on open rather than on click, since a deep link arrives with no pointer event.

Verification

Full CI=true vitest run: 480 files, 3690 tests, all passing. tsc --noEmit clean. New coverage: codec round-trips and rejections for all three routes, trust-class guards, store dispatch (navigate does not wait behind an open prompt, echo dedupe applies), handler effects against the real uiStore, and four bell tests including the double-bell case.

Not live-clicked — the two-bell resolution is proven by a stubbed computed style, since jsdom does not inherit visibility. Worth one pass on a device when the next Android build happens.

Part 3 of #144, navigate half. A third trust class joins confirm and silent:
navigate routes only move the user to a screen they could already reach, so
they act without a prompt but never perform an action — billing opens the
account section and deliberately starts no checkout.

SettingsSection becomes a runtime list so a link's section can be checked
rather than trusted; the store keeps the notification centre's open state so
a link can raise it, which the bell previously held in local state.

The mobile shell keeps a second bell mounted behind `invisible` and the
popover is portalled to the body, so each bell measures its own visibility
and the off-screen one leaves the popover to the visible one.
@kipavy
kipavy merged commit b1d29ed into dev Aug 18, 2026
4 checks passed
@kipavy
kipavy deleted the feat/deeplink-part3-navigate branch August 18, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant